Redux Middleware一瞥

前言

Redux 是如今最流行的状态管理框架之一,其通过单一 Store 和限制状态更新的方式,让状态变得可控可预测。然而,redux 的一系列限制,也让其产生了一些弊端:

  1. 样板式代码过多,书写繁琐
  2. 不支持异步 action
    那么有什么办法来让 redux 支持异步 action 呢。redux 吸收借鉴了 express 和 koa 的 middleware 思想,拥有一套强大的 middleware 系统,通过 middleware,即可实现异步 action 功能。今天,闲来无事,走马观花,重新认识下 redux middleware

Middleware 的用法

先来看看,在 redux 中,它是如何使用的。

1
2
3
4
5
6
import { createStore, combineReducers, applyMiddleware } from "redux";
import reducers from "./reducers";

const App = combineReducers(reducers);
const middlewares = [你的middleware];
const store = createStore(app, applyMiddleware(...middlewares));

很简单,使用时,只要引入 applyMiddleware 函数,然后将你需要使用的 middleware 作为参数传入 applyMiddleware 调用,最后将返回结果作为参数传入 createStore,就完成了 redux 引入 middleware

Middleware 的形式

再来看看,一个 middleware 长什么样,以 redux-thunk 这个最有名的天选 middleware 为例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => next => action => {
if (typeof action === "function") {
return action(dispatch, getState, extraArgument);
}

return next(action);
};
}

const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;

redux-thunk 代码就这么短短几行,为了注入 extraArgument,特意使用的工厂模式,生产最后会导出的 thunk。所以,删减下,这个 thunk middleware 其实长这样

1
2
3
4
5
6
7
8
9
const thunk = ({ dispatch, getState }) => next => action => {
if (typeof action === "function") {
return action(dispatch, getState);
}

return next(action);
};

export default thunk;

结合文档中的其它 middleware,大致能得出一个 middleware 长什么样了

1
({ dispatch, getState })=> (next) => (action) => {}

Middleware 机制的实现

查看 redux 源码中的 middleware 部分,验证下一个 middleware 是不是应该长这样,以及 redux 是如何实现 middleware 机制的。
首先看applyMiddleware.js文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
export default function applyMiddleware(...middlewares) {
return createStore => (...args) => {
const store = createStore(...args);
let dispatch = () => {
throw new Error(
`Dispatching while constructing your middleware is not allowed. ` +
`Other middleware would not be applied to this dispatch.`
);
};

const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
};
const chain = middlewares.map(middleware => middleware(middlewareAPI));
dispatch = compose(...chain)(store.dispatch);

return {
...store,
dispatch
};
};
}

applyMiddleware 是一个高阶函数,利用 ES6 rest 操作符,
将传入的 middleware,收集到一个 middlewares 数组中,然后返回一个新的函数。而这个新的函数则是一个store enhancer, 一个 store enhancer, 接收一个 store creator,返回一个新的 store creator.

1
type StoreEnhancer = (next: StoreCreator) => StoreCreator

新的 store creator 内部,先调用旧的 store creator, 并且定义了一个默认的 dispatch。然后创建一个对象 middlewareAPI,这个对象有两个方法(getState 和 dispath), getState 为原 store.getState 的引用;dispatch 为一个箭头函数,里面会调用默认的 dispatch, 这里用箭头函数包裹,会形成一个闭包, 当默认 dispatch 函数变化的时候,middlewareAPI 中的 dispatch 也会变化。
然后 middlewares.map 一下,执行各个 middleware,为它们注入 middlewareAPI 这个对象,返回新的名叫 chain 的数组。
看到这里,也就知道了,为什么一个中间件会

1
({ dispatch, getState } )=> {}

接下来,调用了一个 compose 函数.

1
2
3
4
5
6
7
8
9
10
11
export default function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg;
}

if (funcs.length === 1) {
return funcs[0];
}

return funcs.reduce((a, b) => (...args) => a(b(...args)));
}

compose 函数通过 rest 将参数收集到一个数组中,然后调用 reduce 这个累计函数,返回一个新的函数。如:

1
compose(f, g, h), 返回(...args) => f(g(h(...args)))

所以,chain 数组中的函数,就实现了如下的调用

1
2
3
4
5
6
const a = (next) => (action) => {};
const b = (next) => (action) => {};
const c = (next) => (action) => {};
const dispatchA = a(dispatch);
const dispatchB = b(dispatchA);
const dispatchC = c(dispatchB);

而这就是 redux middleware 的本质,包装原 dispatch, 返回一个新的 dispatch.

结语

走马观花,简单对 Middleware 的用法,形式,实现进行了一瞥,Middleware 简单而又强大,里面蕴含了函数柯里化的思想; 闭包和 ES5 reduce 函数的使用更是巧妙。水下此文,已备忘。